package org.example.domain.plugin.service;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.example.domain.cache.CacheKeyGenerator;
import org.example.domain.cache.CacheService;
import org.example.domain.config.PanSouProperties;
import org.example.types.model.PluginSearchResult;
import org.example.types.model.SearchContext;
import org.example.types.model.SearchResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;

import javax.annotation.PostConstruct;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;

/**
 * 基础异步插件抽象类
 */
@Slf4j
public abstract class BaseAsyncPlugin implements AsyncSearchPlugin {

    @Autowired
    protected WebClient webClient;

    @Autowired
    @Qualifier("fastWebClient")
    protected WebClient fastWebClient;

    @Autowired
    protected CacheService cacheService;

    @Autowired
    protected CacheKeyGenerator cacheKeyGenerator;

    @Autowired
    protected PanSouProperties panSouProperties;

    @Autowired
    @Qualifier("asyncPluginScheduler")
    protected Scheduler asyncPluginScheduler;

    protected Cache<String, PluginSearchResult> pluginCache;

    @PostConstruct
    public void init() {
        // 初始化插件级别的缓存
        this.pluginCache = Caffeine.newBuilder()
                .maximumSize(1000)
                .expireAfterWrite(Duration.ofHours(1))
                .recordStats()
                .build();

        log.info("BaseAsyncPlugin initialized for: {}", getName());
    }

    @Override
    public Mono<PluginSearchResult> asyncSearch(String keyword, SearchContext context, Map<String, Object> ext) {
        String cacheKey = cacheKeyGenerator.generatePluginSpecificCacheKey(getName(), keyword, ext);

        return Mono.fromCallable(() -> pluginCache.getIfPresent(cacheKey))
                .cast(PluginSearchResult.class)
                .switchIfEmpty(
                    // 缓存未命中，执行实际搜索
                    doSearch(keyword, context, ext)
                        .timeout(getResponseTimeout())
                        .doOnSuccess(result -> {
                            // 更新插件缓存
                            pluginCache.put(cacheKey, result);
                            // 更新主缓存（异步）
                            updateMainCache(context.getMainCacheKey(), result)
                                .subscribeOn(asyncPluginScheduler)
                                .subscribe();
                        })
                        .onErrorResume(TimeoutException.class, ex -> {
                            // 超时处理：返回空结果，后台继续处理
                            doSearchInBackground(keyword, context, ext, cacheKey);
                            return Mono.just(PluginSearchResult.empty(getName()));
                        })
                        .onErrorResume(Exception.class, ex -> {
                            log.warn("Plugin {} search failed for keyword: {}", getName(), keyword, ex);
                            return Mono.just(PluginSearchResult.empty(getName()));
                        })
                );
    }

    /**
     * 子类实现具体搜索逻辑
     */
    protected abstract Mono<PluginSearchResult> doSearch(String keyword, SearchContext context, Map<String, Object> ext);

    /**
     * 后台继续处理搜索
     */
    private void doSearchInBackground(String keyword, SearchContext context, Map<String, Object> ext, String cacheKey) {
        doSearch(keyword, context, ext)
            .timeout(getProcessingTimeout())
            .subscribeOn(asyncPluginScheduler)
            .subscribe(
                result -> {
                    pluginCache.put(cacheKey, result);
                    updateMainCache(context.getMainCacheKey(), result).subscribe();
                    log.debug("Background search completed for plugin: {}, keyword: {}", getName(), keyword);
                },
                error -> log.warn("Background search failed for plugin: {}, keyword: {}", getName(), keyword, error)
            );
    }

    /**
     * 更新主缓存
     */
    protected Mono<Void> updateMainCache(String mainCacheKey, PluginSearchResult result) {
        if (mainCacheKey == null || !result.isFinal()) {
            return Mono.empty();
        }

        return cacheService.set(mainCacheKey, result, panSouProperties.getCache().getTtl())
                .doOnSuccess(v -> log.debug("Updated main cache for plugin: {}, key: {}", getName(), mainCacheKey))
                .doOnError(ex -> log.warn("Failed to update main cache for plugin: {}", getName(), ex))
                .onErrorResume(ex -> Mono.empty());
    }

    /**
     * 过滤结果（基于关键词）
     */
    protected List<SearchResult> filterResults(List<SearchResult> results, String keyword) {
        if (skipServiceFilter() || keyword == null || keyword.trim().isEmpty()) {
            return results;
        }

        String lowerKeyword = keyword.toLowerCase();
        return results.stream()
                .filter(result ->
                    result.getTitle().toLowerCase().contains(lowerKeyword) ||
                    result.getContent().toLowerCase().contains(lowerKeyword)
                )
                .toList();
    }

    /**
     * 创建成功结果
     */
    protected PluginSearchResult createSuccessResult(List<SearchResult> results) {
        return PluginSearchResult.builder()
                .results(results)
                .isFinal(true)
                .timestamp(LocalDateTime.now())
                .source(getName())
                .message("搜索完成")
                .build();
    }

    /**
     * 创建错误结果
     */
    protected PluginSearchResult createErrorResult(String message) {
        return PluginSearchResult.builder()
                .results(Collections.emptyList())
                .isFinal(false)
                .timestamp(LocalDateTime.now())
                .source(getName())
                .message(message)
                .build();
    }

    @Override
    public Duration getProcessingTimeout() {
        return panSouProperties.getAsync().getProcessingTimeout();
    }

    @Override
    public Duration getResponseTimeout() {
        return panSouProperties.getAsync().getResponseTimeout();
    }
}